home *** CD-ROM | disk | FTP | other *** search
/ Mac100% 1998 November / MAC100-1998-11.ISO.7z / MAC100-1998-11.ISO / オンラインソフト定点観測 / ユーティリティ / Mops 3.2.sea / Mops 3.2 / Mops source / More classes / Multitasking < prev    next >
Text File  |  1998-05-02  |  13KB  |  445 lines

  1. ¥ Multitasking.  April 88.
  2.  
  3. ¥ Feb 90 - M stack refs removed for Mops 1.2.
  4.  
  5. (*
  6.  
  7. One of the beauties of a Forth-based system is that it is easy to implement
  8. multitasking.  This allows us to do interesting things like continue processing
  9. while a window is being dragged etc.  Most Mac applications can't manage this. 
  10. It makes our applications look unbelievably sophisticated, and yet this code is
  11. only about 2500 bytes.  This is possible because the Forth approach to
  12. multitasking, as usual, simplifies things considerably compared with other
  13. systems.
  14.  
  15. The main simplifying factor is that the scheme is cooperative.  Tasks cannot be
  16. interrupted at any arbitrary point, but must execute PAUSE to allow other tasks
  17. to have a turn.  This allows the overhead for switching tasks to be just about
  18. 20 machine instructions.
  19.  
  20. This code is based on that in the Laxen/Perry F83, including the extra features
  21. added by yours truly in the PDP-11 implementation, notably the mechanism to
  22. keep track of the status of a task.  We make some necessary Mac and Mops
  23. adaptations here - in particular, a task becomes a Mops object, and we set the
  24. various user hook locations (e.g. DragHook) to point to a routine to run the
  25. task round-robin.  Another addition is that each task has a queue of tasks
  26. waiting on it.  This avoids a waiting task having to waste time testing the
  27. other task each time round.  A waiting task can now be put to sleep, and the
  28. time penalty for each waiting task is reduced to one machine JMP instruction
  29. each time round the task loop. If it was worth it, we could even remove the
  30. task from the loop altogether, but it probably isn't worth it.
  31.  
  32. Another necessary Mac adjustment is that we must distinguish between a
  33. foreground task and various background tasks.  Any time WaitNextEvent is
  34. called, all kinds of things can happen which can use several K of stack space. 
  35. Therefore we assume in allocating this space that WNE will only be called from
  36. the foreground task, and we don't need to allocate as much stack space for
  37. background tasks.  Also, the hook procedures must be able to ensure that WNE
  38. will not be called during their execution (very illegal).  If we call WNE only
  39. from the foreground task, there's no problem.  The hook procedures then always
  40. execute as part of the foreground task (they're called by the system at WNE
  41. time), and even if they give background tasks some time before they return,
  42. these background tasks won't be calling WNE.
  43.  
  44. Another thing to watch is that a background task shouldn't do any drawing to
  45. the screen  Not only doesn't it know which GrafPort is switched in, I have also
  46. found QuickDraw doing some strange things in the "unused" area of the stack
  47. between BufPtr and the current A7 stack pointer!!  We therefore now put the
  48. foreground stack LOWEST in memory - this necessitates moving the stack when a
  49. background task is allocated, but at least it avoids any problems with
  50. QuickDraw, since, assuming QuickDraw calls only result from foreground tasks,
  51. the stack pointer at system call time will really represent the lowest address
  52. we need for any of our stacks.↵
  53. Our general philosophy, then, is that the foreground task will look after the
  54. user interface, do all drawing to the screen, and manage the event loop.  It
  55. will delegate any lengthy computation to background tasks, which therefore just
  56. function as computing engines for the foreground.
  57.  
  58. Things would have been a lot easier if we could have allocated a heap block for
  59. each background task's stack.  But then we would get caught by the VBL "stack
  60. sniffer" routine, which would find SP pointing below ApplLimit, think that the
  61. stack had encroached into the heap zone, and politely bow out with system error
  62. 28 (stack collides with heap).
  63.  
  64. ¥            ================================
  65.  
  66. ¥ Here we define values for the space to be allocated for the Mops stacks for new tasks.  These values may be changed as required.  It's better to err on the big side.  Remember that any Toolbox calls can use a lot of data stack space.
  67.  
  68.  2000    value    R_SPACE
  69.  3000    value    S_SPACE
  70.  
  71. 16000    value    FGD_S_SPACE
  72.  
  73.     0    value    REAL_RP0
  74.     0    value    NEW_SP
  75.  
  76.  
  77. ¥ Possible task statuses:
  78.  
  79. type{  AWAKE  ASLEEP  WAITING  STOPPED  TERMINATED  AVAILABLE  CRASHED  }
  80.  
  81.  
  82. ¥ Constants for the 68000 opcodes we need:
  83.  
  84. $ 4EF9 constant    QJMP        ¥ JMP (absolute long)
  85. $ 6104 constant    QBSR        ¥ BSR  +4
  86.  
  87. objPtr    THIS_TASK        ¥ Points to the currently running task.
  88.  
  89. objPtr    TSK1            ¥ Used for tracking task queues.
  90. objPtr    TSK2            ¥  Will be set to class Task.
  91.  
  92.     0    value    STP        ¥ Stack allocation pointer.
  93.  
  94.  
  95. ¥            ====================
  96.  
  97. :code  SUSPEND
  98.     movem.l    d2-d7/a2/a5/a7,-(a6)        ; Save all relevant regs
  99.     movem.l    dic[ExtraLocals],d0-d7/a0/a1    ; Save ExtraLocals area
  100.     movem.l    d0-d7/a0/a1,-(a6)
  101.     movem.l    40(dic[ExtraLocals]),d0-d7/a0/a1
  102.     movem.l    d0-d7/a0/a1,-(a6)
  103.     move    dic[this_task],a1
  104.     move    a6,12(a1)    ; Save data stk ptr in task object
  105.     move    2(a1),a0
  106.     jmp    (a0)    ; JMP to LINK to restart next task.
  107. ;code
  108.  
  109. :code  RESTART
  110.     move    (a7)+,a1
  111.     subq    #2,a1    ; A1 -> task object addr
  112.     move    18(a1),dic[SP0]    ; Set SP0
  113.     move    22(a1),dic[RP0]    ; Set RP0
  114.     move    12(a1),a6    ; Set SP
  115.     lea    rel[this_task],a0    ; We may be based on A5, not set up yet
  116.     move.l    a1,(a0)
  117.     movem.l    (a6)+,d0-d7/a0/a1        ; Restore ExtraLocals area
  118.     movem.l    d0-d7/a0/a1,40(dic[ExtraLocals]
  119.     movem.l    (a6)+,d0-d7/a0/a1
  120.     movem.l    d0-d7/a0/a1,dic[ExtraLocals]
  121.     movem.l    (a6)+,d2-d7/a2/a5/a7        ; Restore saved regs
  122.     rts
  123. ;code
  124.  
  125.  
  126. : NoRoom    159 die  ;
  127.  
  128. :code  MOVE_TASKS    ¥ ( dist -- )
  129.     loc
  130.     pop.l    d1    ; D1 = distance to move
  131.     move.l    a6,d0
  132.     sub.l    d1,d0    ; D0 = tentative destination
  133.     cmp.l    glob[ApplLimit],d0
  134.     blo.s    dic[noRoom]
  135.     sub.l    d1,dic[SP0]
  136.     sub.l    d1,dic[RP0]
  137.     move.l    d0,a1    ; A1 -> destination
  138.     move.l    dic[real_RP0],d0
  139.     sub.l    a6,d0    ; D0 = #bytes to move
  140.     move.l    a7,a0
  141.     sub.l    d1,a0
  142.     move.l    a0,dic[new_SP]
  143.     move.l    a6,a0    ; A0 -> source
  144.     move.l    a1,a7    ; Set A7 low in case of an interrupt during
  145.     move.l    a1,a6    ;  the loop
  146.     addq.l    #8,d0
  147. loop    move.l    (a0)+,(a1)+
  148.     subq.l    #4,d0
  149. lptest    bgt.s    loop
  150.     move.l    dic[new_SP],a7
  151. ;code
  152.  
  153.  
  154. forward    CRASH
  155. forward    NOWHERE
  156.  
  157.  
  158. :class    TASK    super{ object }
  159. record
  160. {    int    ENTRY
  161.     var    LINK
  162.     int    JMP_CODE
  163.     var    ^RESTART
  164.     var    ^SP
  165.     int    STATUS
  166.     var    tSP0
  167.     var    tRP0
  168.     var    QUEUE
  169.     var    QLINK
  170.     int    QSTATUS
  171. }
  172.  
  173. ' this_task    set_to_class  task
  174. ' tsk1        set_to_class  task
  175. ' tsk2        set_to_class  task
  176.  
  177.  
  178. :m (SLEEP):        QJMP  put: entry  ;m
  179. :m SLEEP:        asleep  put: status   (sleep): self  ;m
  180. :m WAKE:        QBSR  put: entry    awake  put: status  ;m
  181.  
  182. :m NEXT:        get: link  ;m
  183. :m SETNEXT:    put: link  ;m
  184.  
  185. :m NEXTQ:        get: Qlink  ;m
  186. :m SETNEXTQ:    put: Qlink  ;m
  187.  
  188. :m ?RESUME:    ¥ ( status# -- b )
  189.     get: Qstatus  >=  dup
  190.     IF  wake: self  THEN  ;m
  191.  
  192. :m RELEASEQ:
  193.     nilP -> tsk1   get: queue  -> tsk2
  194.     BEGIN
  195.         tsk2  nilP =  ?EXIT
  196.         get: status  ?resume: tsk2
  197.         IF  ( resumed - remove from queue )
  198.             nextQ: tsk2
  199.             tsk1  nilP =
  200.             IF  put: queue  ELSE  setnextQ: tsk1  THEN
  201.         THEN
  202.         tsk2 -> tsk1  nextQ: tsk2  -> tsk2
  203.     AGAIN  ;m
  204.  
  205.  
  206. :m (WAIT):    ¥ ( status# -- )  Used by Wait: - see below.
  207.     put: Qstatus  waiting  put: status  releaseQ: self
  208.     (sleep): self  ;m
  209.  
  210. :m WAIT:    ¥ ( status# -- ).
  211.     ¥ If the given status# is greater than the status of SELF, the currently
  212.     ¥ running task is queued and put to sleep.  It will be woken when the
  213.     ¥ status of SELF goes to the given status# or higher.  If the given status#
  214.     ¥ is less than or equal to the status of SELF, we don't queue this_task,
  215.     ¥ since the condition it wishes to wait for has already occurred.  However
  216.     ¥ we make it do a "phantom" wait so that its own queue will be released.  
  217.     ¥ Logically it has waited, so any tasks waiting for it to wait, must be
  218.     ¥ released.
  219.  
  220.     dup  (wait): this_task
  221.     get: status  <=
  222.     IF    wake: this_task
  223.     ELSE
  224.         get: queue  setnextQ: this_task
  225.         this_task  put: queue  
  226.         next_task
  227.     THEN  ;m
  228.  
  229. :m STATUS:    get: status  ;m
  230. :m SETSTATUS:    put: status  releaseQ: self  ;m
  231.  
  232. :m ASSIGN:  { PC ¥ sptr -- }
  233.     get: status  available  <>  abort" Task not available"
  234.         ¥ Now we set up a "saved reg" image so that it looks like
  235.         ¥ we've been suspended with PC as the return address.
  236.     get: tSP0  -> sptr
  237.     -4 ++> sptr
  238.     get: tRP0 4-  ['] nowhere    over !    ¥ Initial higher rtn addr
  239.          4-  PC        over !    ¥ Initial rtn addr
  240.                     sptr !    ¥ Initial A7 = rtn stk ptr
  241.       -4 ++> sptr    modbase        sptr !    ¥ Initial A5 = modbase
  242.       -4 ++> sptr    -1        sptr !    ¥ Initial A2 - here's hoping!
  243.     -104 ++> sptr                ¥ Room for D2-D7 and ExtraLocals
  244.     sptr  put: ^SP
  245.     sleep: self  ;m
  246.  
  247. :m RESET:
  248.     available  put: status   ;m
  249.  
  250. :m DISPLACE:  { dist -- }
  251.     dist -: ^SP
  252.     dist -: tSP0
  253.     dist -: tRP0  ;m
  254.     
  255. ¥ NEW: sets up various items in this task object, which are dependent on
  256. ¥ the current Mops base and stack location.  These can't be determined
  257. ¥ until run time, especially under MultiFinder.  Each task must be
  258. ¥ initialized at run time with NEW:, starting with FOREGROUND.  Note:
  259. ¥ FOREGROUND MUST BE FIRST.
  260.  
  261. :m NEW:
  262.     ['] restart  put: ^restart
  263.     this_task  nilP =
  264.     IF  ¥ This is the first one, i.e FOREGROUND
  265.         ^base  setnext: self
  266.         ^base  -> this_task        ¥ Point LINK to ourselves
  267.         sp@ -> stp            ¥ Set initial stp ready for backgd tasks
  268.         SP0  put: tSP0
  269.         RP0  put: tRP0
  270.         RP0 -> real_RP0
  271.         wake: self
  272.     ELSE
  273.         R_space S_space +  dup  move_tasks
  274.         this_task -> tsk1        ¥ Ought to be Foreground
  275.         BEGIN
  276.             dup  displace: tsk1
  277.             next: tsk1  -> tsk1
  278.             tsk1 this_task =
  279.         UNTIL  drop
  280.         next: this_task  setnext: self
  281.         ^base  setnext: this_task    ¥ Link ourselves into chain
  282.         real_RP0  dup    put: tRP0
  283.         R_space -    put: tSP0
  284.         available  put: status
  285.         ['] crash  assign: self      ¥ In case we wake: prematurely
  286.         available  put: status      ¥ not really asleep
  287.     THEN  ;m
  288.  
  289. :m .Q:
  290.     get: queue  -> tsk1
  291.     tsk1 nilP =  IF  ." empty"  EXIT  THEN
  292.     BEGIN
  293.         tsk1 nilP =  ?EXIT
  294.         .id: tsk1  space  tsk1 .h space
  295.         nextq: tsk1  -> tsk1
  296.     AGAIN  ;m
  297.  
  298. :m .STATUS:
  299.     1000  get: status  getIndStr  type  ;m
  300.  
  301. :m DUMP:
  302.     .class: self  3 spaces  .id: self
  303.     ."   status: "  .status: self
  304.     ."   queue: "   .q: self  cr  ;m
  305.  
  306.  
  307. :m CLASSINIT:
  308.     qJMP  put: JMP_code
  309.     nilP  put: queue  ;m
  310.  
  311. ;class
  312.  
  313. ¥ Now create task FOREGROUND as the currently running task:
  314.  
  315. task  FOREGROUND
  316.  
  317. ¥ Now we set up the user hooks so that if we are multitasking, other tasks
  318. ¥ can keep running while windows are being dragged or menus being selected.
  319.  
  320. $ A30    constant    MENUHOOK
  321. $ 9F6    constant    DRAGHOOK
  322.  
  323. :proc RUN_THEM   suspend   ;proc
  324.  
  325. :proc (SFD)
  326.     drop        ¥ Dlg ptr not needed
  327.     i->l >r        ¥ Item #
  328.     word0 drop    ¥ Left for return result - don't need it now
  329.     r 100 =
  330.     IF  ( null event )  next_task  THEN
  331.     r> makeint  ;proc
  332.  
  333.  
  334. ¥ MULTI and SINGLE turn multitasking on and off respectively.  MULTI, among
  335. ¥ other things, redirects PAUSE to just switch tasks.  Without multitasking,
  336. ¥ we make PAUSE call next: fEvent, but with multitasking, this becomes
  337. ¥ the foreground task's sole responsibility, and we mustn't do it anywhere
  338. ¥ else.
  339.  
  340. : MULTI
  341.     ['] suspend  -> next_task  ['] suspend  -> pause
  342.     ['] run_them  dup  MenuHook !  DragHook !
  343.     ['] (sfd)  -> SFdlgHook
  344.     0 -> sleepticks  ;
  345.  
  346. : SINGLE
  347.     ['] null  -> next_task  ['] (pause)  -> pause
  348.     0 MenuHook !  0 DragHook !
  349.     0 -> SFdlgHook
  350.     20 -> sleepticks  ;
  351.  
  352.  
  353. ¥ Task manipulation
  354.  
  355. : (STOP)        (sleep): this_task  suspend   ;
  356. : STOP        stopped  setStatus: this_task
  357.         releaseQ: this_task  (stop)   ;
  358.  
  359. :f CRASH
  360.     BEGIN
  361. ¥        3 beep  ." !! no code assigned to task " .id: this_task
  362.         crashed  setStatus: this_task  (stop)
  363.     AGAIN  ;f
  364.  
  365. :f NOWHERE    ¥ A running task at its top level has really been called
  366.             ¥ from nowhere.  So we define NOWHERE so that if it returns,
  367.             ¥ it will actually go to NOWHERE, which is somewhere, not just
  368.             ¥ anywhere.  (I hope that's clear.)
  369.             ¥ We define this as normal termination of a task.  Any attempt
  370.             ¥  to wake: a terminated task causes CRASH to be executed.
  371.  
  372.     terminated  setStatus: this_task
  373.     releaseQ: this_task  (stop)  crash  ;f
  374.  
  375.  
  376. : .TASKS
  377.     foreground
  378.     BEGIN
  379.         dup dump: **  next: **
  380.         dup foreground =
  381.     UNTIL  drop   ;
  382.  
  383. : CLTSK        ¥ This is called on an abort.  We execute the normal
  384.         ¥ abort action, then stop the currently
  385.         ¥ running task and set its status to crashed, unless
  386.         ¥ it's Foreground (which we'd better not stop)!
  387.  
  388.     cl3                ¥ Previous abort action
  389.     this_task nilP =  ?EXIT        ¥ Out if nothing initialized
  390.     this_task foreground =  ?EXIT    ¥ Or if this is foreground
  391.     crashed  setStatus: this_task  ['] crash >r
  392.     releaseQ: this_task  (stop)  ;
  393.  
  394. ' clTsk -> abortVec
  395.  
  396. ¥ endload
  397.  
  398. ¥ TESTING:
  399.  
  400. task  T1   task  T2
  401.  
  402.  0 value    CNT
  403. 10 value    CNT1
  404.  0 value    CNT2
  405. 10 value    CNT3
  406.  
  407. file F
  408.  
  409. : HAHA
  410.     1 2 3
  411.     BEGIN
  412.         next_task  cnt
  413.         NIF    500 -> cnt  -1 ++> cnt1
  414.             ." haha " cr
  415. ¥            waiting wait: t2
  416.         ELSE    -1 ++> cnt
  417.         THEN
  418.     cnt1
  419.     NUNTIL  ;
  420.  
  421. : HOHO
  422.     -4 -5 -6
  423.     BEGIN
  424.         next_task  cnt2
  425.         NIF
  426.             800 -> cnt2  -1 ++> cnt3
  427.             ." hoho " cr
  428. ¥            waiting wait: t1
  429.         ELSE  -1 ++> cnt2
  430.         THEN
  431.     cnt3
  432.     NUNTIL  ;
  433.  
  434. : GO
  435.     new: foreground  new: t1  new: t2
  436. ¥    ['] haha  assign: t1
  437. ¥    ['] hoho  assign: t2
  438.     wake: t1  ( wake: t2 )
  439.     multi
  440. ¥    'type TEXT 1  stdGet: f  drop
  441. ;
  442.  
  443. : QQ    wake: t2  ;
  444.